package org.ricks.net.util;

// 高效的MurmurHash3实现
public final class MurmurHash {
    private static final long C1 = 0x87c37b91114253d5L;
    private static final long C2 = 0x4cf5ad432745937fL;
    private static final int R1 = 31;
    private static final int R2 = 27;
    private static final int M = 5;
    private static final int N1 = 0x52dce729;
    private static final int N2 = 0x38495ab5;

    public static long hash64(String data) {
        byte[] bytes = data.getBytes();
        return hash64(bytes, 0, bytes.length, 0);
    }

    public static long hash64(byte[] data, int offset, int length, int seed) {
        long h1 = seed & 0xFFFFFFFFL;
        long h2 = seed & 0xFFFFFFFFL;
        int roundedEnd = offset + (length & 0xFFFFFFF0);

        for (int i = offset; i < roundedEnd; i += 16) {
            long k1 = getLong(data, i);
            long k2 = getLong(data, i + 8);

            k1 *= C1; k1 = Long.rotateLeft(k1, R1); k1 *= C2;
            h1 ^= k1; h1 = Long.rotateLeft(h1, R2); h1 += h2;
            h1 = h1 * M + N1;

            k2 *= C2; k2 = Long.rotateLeft(k2, 33); k2 *= C1;
            h2 ^= k2; h2 = Long.rotateLeft(h2, 31); h2 += h1;
            h2 = h2 * M + N2;
        }

        long k1 = 0, k2 = 0;
        switch (length & 15) {
            case 15: k2 ^= ((long) data[roundedEnd + 14]) << 48;
            case 14: k2 ^= ((long) data[roundedEnd + 13]) << 40;
            case 13: k2 ^= ((long) data[roundedEnd + 12]) << 32;
            case 12: k2 ^= ((long) data[roundedEnd + 11]) << 24;
            case 11: k2 ^= ((long) data[roundedEnd + 10]) << 16;
            case 10: k2 ^= ((long) data[roundedEnd + 9]) << 8;
            case  9: k2 ^= data[roundedEnd + 8];
                k2 *= C2; k2 = Long.rotateLeft(k2, 33); k2 *= C1; h2 ^= k2;
            case  8: k1 ^= ((long) data[roundedEnd + 7]) << 56;
            case  7: k1 ^= ((long) data[roundedEnd + 6]) << 48;
            case  6: k1 ^= ((long) data[roundedEnd + 5]) << 40;
            case  5: k1 ^= ((long) data[roundedEnd + 4]) << 32;
            case  4: k1 ^= ((long) data[roundedEnd + 3]) << 24;
            case  3: k1 ^= ((long) data[roundedEnd + 2]) << 16;
            case  2: k1 ^= ((long) data[roundedEnd + 1]) << 8;
            case  1: k1 ^= data[roundedEnd];
                k1 *= C1; k1 = Long.rotateLeft(k1, R1); k1 *= C2; h1 ^= k1;
        }

        h1 ^= length; h2 ^= length;
        h1 += h2; h2 += h1;
        h1 = fmix(h1); h2 = fmix(h2);
        h1 += h2;
        return h1;
    }

    private static long fmix(long h) {
        h ^= h >>> 33; h *= 0xff51afd7ed558ccdL;
        h ^= h >>> 33; h *= 0xc4ceb9fe1a85ec53L;
        h ^= h >>> 33; return h;
    }

    private static long getLong(byte[] data, int offset) {
        return ((long) data[offset] & 0xFF) | (((long) data[offset + 1] & 0xFF) << 8)
                | (((long) data[offset + 2] & 0xFF) << 16) | (((long) data[offset + 3] & 0xFF) << 24)
                | (((long) data[offset + 4] & 0xFF) << 32) | (((long) data[offset + 5] & 0xFF) << 40)
                | (((long) data[offset + 6] & 0xFF) << 48) | (((long) data[offset + 7] & 0xFF) << 56);
    }
}